//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

namespace Microsoft.Research.ScientificWorkflow.SecurityControls
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.ComponentModel;
    using System.Windows;
    using System.Windows.Input;
    using ManagementStudioServices.Interfaces;
    using Microsoft.Research.ScientificWorkflow.TridentModel;
    using Microsoft.Research.ScientificWorkflow.TridentUtilities;
    using Microsoft.Research.ScientificWorkflow.UIDesigner.WpfComposite;
    using Microsoft.Research.DataLayer;
    using Microsoft.Research.ScientificWorkflow.TridentUtilities.Properties;

    public class SecurityPolicyPresenter : DependencyObject, INotifyPropertyChanged
    {
        #region Private Variables

        /// <summary>
        /// Security Policy window.
        /// </summary>
        private SecurityPolicyView systemSecurity;

        /// <summary>
        /// 
        /// </summary>
        private bool anonymousEnabled;

        /// <summary>
        /// 
        /// </summary>
        private bool autoCreateUserIfGroupMatch;

        /// <summary>
        /// 
        /// </summary>
        private SystemPolicy policyObject;

        /// <summary>
        /// The workflow manager service instance.
        /// </summary>
        private IRegistryManagerService registryManagerService;

        /// <summary>
        /// 
        /// </summary>
        private UsersAndGroupsPicker picker = null;

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets or sets a value indicating whether [anonymous enabled].
        /// </summary>
        /// <value><c>true</c> if [anonymous enabled]; otherwise, <c>false</c>.</value>
        public bool AnonymousEnabled
        {
            get
            {
                return this.anonymousEnabled;
            }
            set
            {
                this.anonymousEnabled = value;
                this.OnNotifyPropertyChanged("AnonymousEnabled");
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether [auto create user if group match].
        /// </summary>
        /// <value>
        /// 	<c>true</c> if [auto create user if group match]; otherwise, <c>false</c>.
        /// </value>
        public bool AutoCreateUserIfGroupMatch
        {
            get
            {
                return this.autoCreateUserIfGroupMatch;
            }
            set
            {
                this.autoCreateUserIfGroupMatch = value;
                this.OnNotifyPropertyChanged("AutoCreateUserIfGroupMatch");
            }
        }

        /// <summary>
        /// Gets or sets the administrators.
        /// </summary>
        /// <value>The administrators.</value>
        public ObservableCollection<SecurityPolicyModel> Administrators { get; private set; }

        /// <summary>
        /// Gets or sets the users.
        /// </summary>
        /// <value>The users.</value>
        public ObservableCollection<SecurityPolicyModel> Users { get; private set; }

        /// <summary>
        /// Gets or sets the guests.
        /// </summary>
        /// <value>The guests.</value>
        public ObservableCollection<SecurityPolicyModel> Guests { get; private set; }

        /// <summary>
        /// Gets or sets the administrators.
        /// </summary>
        /// <value>The administrators.</value>
        public List<SecurityPolicyModel> InitialAdministrators { get; private set; }

        /// <summary>
        /// Gets or sets the users.
        /// </summary>
        /// <value>The users.</value>
        public List<SecurityPolicyModel> InitialUsers { get; private set; }

        /// <summary>
        /// Gets or sets the guests.
        /// </summary>
        /// <value>The guests.</value>
        public List<SecurityPolicyModel> InitialGuests { get; private set; }

        /// <summary>
        /// Gets or sets the selected list.
        /// </summary>
        /// <value>The selected list.</value>
        public ObservableCollection<SecurityPolicyModel> SelectedList { get; private set; }

        /// <summary>
        /// Gets or sets the final list.
        /// </summary>
        /// <value>The selected list.</value>
        public ObservableCollection<SecurityPolicyModel> InitialList { get; private set; }

        /// <summary>
        /// 
        /// </summary>
        ISecurityPrincipalColection securityPrincipalCollection = null;


        #region Commands

        /// <summary>
        /// Gets the close schedule window command.
        /// </summary>
        /// <value>The close schedule window command.</value>
        public ICommand UpdatePolicyCommand { get; private set; }

        /// <summary>
        /// The command executed to show the provenance information.
        /// </summary>
        public ICommand ShowPickerCommand { get; private set; }

        /// <summary>
        /// Gets or sets the add user command.
        /// </summary>
        /// <value>The add user command.</value>
        public ICommand AddUserCommand { get; private set; }

        /// <summary>
        /// Gets or sets the add guest command.
        /// </summary>
        /// <value>The add guest command.</value>
        public ICommand AddGuestCommand { get; private set; }

        /// <summary>
        /// Gets or sets the add administrator command.
        /// </summary>
        /// <value>The add administrator command.</value>
        public ICommand AddAdministratorCommand { get; private set; }

        /// <summary>
        /// Gets or sets the remove user command.
        /// </summary>
        /// <value>The remove user command.</value>
        public DelegateCommand<object> RemoveUserCommand { get; private set; }

        /// <summary>
        /// Gets or sets the remove guest command.
        /// </summary>
        /// <value>The remove guest command.</value>
        public DelegateCommand<object> RemoveGuestCommand { get; private set; }

        /// <summary>
        /// Gets or sets the remove administrator command.
        /// </summary>
        /// <value>The remove administrator command.</value>
        public DelegateCommand<object> RemoveAdministratorCommand { get; private set; }

        /// <summary>
        /// Gets or sets the save policy command.
        /// </summary>
        /// <value>The save policy command.</value>
        public ICommand SavePolicyCommand { get; private set; }

        /// <summary>
        /// Gets or sets the close schedule window command.
        /// </summary>
        /// <value>The close schedule window command.</value>
        public ICommand CloseScheduleWindowCommand { get; private set; }

        #endregion

        #endregion

        #region Constructor
        /// <summary>
        /// Initializes a new instance of the <see cref="SecurityPolicyPresenter"/> class.
        /// </summary>
        /// <param name="policyObject">The policy object.</param>
        /// <param name="workflowManagerService">The workflow manager service.</param>
        public SecurityPolicyPresenter(SystemPolicy policyObject, IRegistryManagerService registryManagerService)
        {
            try
            {
                Initialize(policyObject);

                this.registryManagerService = registryManagerService;

                systemSecurity = new SecurityPolicyView();
                systemSecurity.DataContext = this;

                #region Commands Initialization

                this.ShowPickerCommand = new DelegateCommand<object>(p => this.ShowUserAndGroupPicker());
                this.AddUserCommand = new DelegateCommand<object>(p => this.AddUser());
                this.AddGuestCommand = new DelegateCommand<object>(p => this.AddGuest());
                this.AddAdministratorCommand = new DelegateCommand<object>(p => this.AddAdministrator());

                this.RemoveUserCommand = new DelegateCommand<object>(
                    p => this.RemoveUser(),
                    c => RemoveUserCanExecute());

                this.RemoveGuestCommand = new DelegateCommand<object>(
                    p => this.RemoveGuest(),
                    c => RemoveGuestCanExecute());

                this.RemoveAdministratorCommand = new DelegateCommand<object>(
                    p => this.RemoveAdministrator(),
                    c => RemoveAdministratorCanExecute());

                this.SavePolicyCommand = new DelegateCommand<object>(p => this.SavePolicy());
                this.CloseScheduleWindowCommand = new DelegateCommand<object>(p => this.CloseSystemPolicyUI());

                #endregion

                systemSecurity.Owner = System.Windows.Application.Current.MainWindow;
                systemSecurity.ShowDialog();
            }
            catch (TridentCustomException ex)
            {
                TridentErrorHandler.HandleTridentCustomExceptionInUI(ex);
            }
            catch (Exception ex)
            {
                TridentErrorHandler.HandleKnownExceptionsInUI(ex);
            }
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Initializes the System Policy with policy object.
        /// </summary>
        /// <param name="policyObject">The policy object.</param>
        private void Initialize(SystemPolicy policyObject)
        {
            this.policyObject = policyObject;

            this.AnonymousEnabled = policyObject.AnonymousEnabled;
            this.AutoCreateUserIfGroupMatch = policyObject.AutoCreateUserIfGroupMatch;

            this.Administrators = new ObservableCollection<SecurityPolicyModel>();
            this.Users = new ObservableCollection<SecurityPolicyModel>();
            this.Guests = new ObservableCollection<SecurityPolicyModel>();

            this.InitialUsers = new List<SecurityPolicyModel>();
            this.InitialGuests = new List<SecurityPolicyModel>();
            this.InitialAdministrators = new List<SecurityPolicyModel>();

            // Add administrators from registry to UI list. 
            foreach (ISecurityPrincipal administrator in policyObject.TridentAdministrators)
            {
                if (!((INamedObject)administrator).Name.Equals("system"))
                {
                    SecurityPolicyModel adminModel = new SecurityPolicyModel(administrator);
                    this.Administrators.Add(adminModel);
                    this.InitialAdministrators.Add(adminModel);
                }
            }

            // Add users from registry to UI list. 
            foreach (ISecurityPrincipal user in policyObject.TridentUsers)
            {
                SecurityPolicyModel userModel = new SecurityPolicyModel(user);
                this.Users.Add(userModel);
                this.InitialUsers.Add(userModel);
            }

            // Add guests from registry to UI list. 
            foreach (ISecurityPrincipal guest in policyObject.TridentGuests)
            {
                if (!((INamedObject)guest).Name.Equals("Anonymous"))
                {
                    SecurityPolicyModel guestModel = new SecurityPolicyModel(guest);
                    this.Guests.Add(guestModel);
                    this.InitialGuests.Add(guestModel);
                }
            }
        }

        #region Add Security Principal

        /// <summary>
        /// Adds the user to list.
        /// </summary>
        private void AddUser()
        {
            this.SelectedList = this.Users;
            this.ShowUserAndGroupPicker();
        }

        /// <summary>
        /// Adds the guest to list.
        /// </summary>
        private void AddGuest()
        {
            this.SelectedList = this.Guests;
            this.ShowUserAndGroupPicker();
        }

        /// <summary>
        /// Adds the administrator to list.
        /// </summary>
        private void AddAdministrator()
        {
            this.SelectedList = this.Administrators;
            this.ShowUserAndGroupPicker();
        }

        /// <summary>
        /// Adds the security principal.
        /// </summary>
        /// <param name="securityPrincipal">The security principal.</param>
        private void AddSecurityPrincipal(ISecurityPrincipal securityPrincipal, SystemRoleType selectdRoleType)
        {
            ISecurityPrincipalColection securityPrincipalColection = null;

            switch (selectdRoleType)
            {
                case SystemRoleType.TridentAdministrators:
                    securityPrincipalColection = this.policyObject.TridentAdministrators;
                    break;
                case SystemRoleType.TridentGuests:
                    securityPrincipalColection = this.policyObject.TridentGuests;
                    break;
                case SystemRoleType.TridentUsers:
                    securityPrincipalColection = this.policyObject.TridentUsers;
                    break;
            }

            securityPrincipalColection.Add(securityPrincipal);
        }

        #endregion

        #region Remove Security Principal

        /// <summary>
        /// Removes the administrator from list.
        /// </summary>
        private void RemoveAdministrator()
        {
            if ((this.systemSecurity.adminList.SelectedItem is SecurityPolicyModel) &&
                !((this.systemSecurity.adminList.SelectedItem as SecurityPolicyModel).SecurityPrincipal as INamedObject).Name.Equals(Environment.UserName))
            {
                this.SelectedList = this.Administrators;
                securityPrincipalCollection = this.policyObject.TridentAdministrators;
                RemoveSecurityPrincipal(this.systemSecurity.adminList.SelectedItem as SecurityPolicyModel);
            }
            else
            {
                TridentMessageBox.ShowTridentErrorMessageBox(ManagementStudio.ErrorCannotDeleteLoggedInUser);
            }
        }

        /// <summary>
        /// Removes the guest from list.
        /// </summary>
        private void RemoveGuest()
        {
            this.SelectedList = this.Guests;
            securityPrincipalCollection = this.policyObject.TridentGuests;
            RemoveSecurityPrincipal(this.systemSecurity.guestList.SelectedItem as SecurityPolicyModel);
        }

        /// <summary>
        /// Removes the user from list.
        /// </summary>
        private void RemoveUser()
        {
            this.SelectedList = this.Users;
            securityPrincipalCollection = this.policyObject.TridentUsers;
            RemoveSecurityPrincipal(this.systemSecurity.userList.SelectedItem as SecurityPolicyModel);
        }

        /// <summary>
        /// Removes the security principal.
        /// </summary>
        /// <param name="securityPrincipal">The security principal.</param>
        private void RemoveSecurityPrincipal(SecurityPolicyModel securityPolicyModel)
        {
            foreach (SecurityPolicyModel model in this.SelectedList)
            {
                if (model.SecurityPrincipal == securityPolicyModel.SecurityPrincipal)
                {
                    this.SelectedList.Remove(model);
                    break;
                }
            }
        }

        /// <summary>
        /// Removes the administrator can execute.
        /// </summary>
        /// <returns></returns>
        private bool RemoveAdministratorCanExecute()
        {
            return CanDelete((this.systemSecurity.adminList.SelectedItem as SecurityPolicyModel));
        }

        /// <summary>
        /// Removes the user can execute.
        /// </summary>
        /// <returns></returns>
        private bool RemoveUserCanExecute()
        {
            return CanDelete((this.systemSecurity.userList.SelectedItem as SecurityPolicyModel));
        }

        /// <summary>
        /// Removes the guest can execute.
        /// </summary>
        /// <returns></returns>
        private bool RemoveGuestCanExecute()
        {
            return CanDelete((this.systemSecurity.guestList.SelectedItem as SecurityPolicyModel));
        }

        /// <summary>
        /// Determines whether this instance can delete the specified security principal.
        /// </summary>
        /// <param name="securityPrincipal">The security principal.</param>
        /// <returns>
        /// 	<c>true</c> if this instance can delete the specified security principal; otherwise, <c>false</c>.
        /// </returns>
        private static bool CanDelete(SecurityPolicyModel securityPrincipal)
        {
            return (securityPrincipal != null) && !(securityPrincipal.IsBuiltin);
        }

        #endregion

        /// <summary>
        /// Saves the updated policy to registry.
        /// </summary>
        private void SavePolicy()
        {
            try
            {
                this.policyObject.AnonymousEnabled = this.AnonymousEnabled;
                this.policyObject.AutoCreateUserIfGroupMatch = this.AutoCreateUserIfGroupMatch;

                this.UpdateUsers();

                this.policyObject.Save();
                systemSecurity.Close();
            }
            catch (TridentCustomException ex)
            {
                TridentErrorHandler.HandleTridentCustomExceptionInUI(ex);
            }
            catch (Exception ex)
            {
                TridentErrorHandler.HandleKnownExceptionsInUI(ex);
            }
        }

        /// <summary>
        /// Updates security users in registry.
        /// </summary>
        private void UpdateUsers()
        {
            foreach (SecurityPolicyModel securityPrincipal in InitialAdministrators)
            {
                if (!this.Administrators.Contains(securityPrincipal))
                {
                    // Check before remove. 
                    securityPrincipalCollection.currentSystemRole = SystemRoleType.TridentAdministrators;
                    securityPrincipalCollection.Remove(securityPrincipal.SecurityPrincipal);


                }
            }

            foreach (SecurityPolicyModel securityPrincipal in InitialUsers)
            {
                if (!this.Users.Contains(securityPrincipal))
                {
                    securityPrincipalCollection.currentSystemRole = SystemRoleType.TridentUsers;
                    // Check before remove.
                    securityPrincipalCollection.Remove(securityPrincipal.SecurityPrincipal);

                }
            }

            foreach (SecurityPolicyModel securityPrincipal in InitialGuests)
            {
                if (!this.Guests.Contains(securityPrincipal))
                {
                    securityPrincipalCollection.currentSystemRole = SystemRoleType.TridentGuests;
                    // Check before remove.
                    securityPrincipalCollection.Remove(securityPrincipal.SecurityPrincipal);
                }
            }

            foreach (SecurityPolicyModel securityPrincipal in Administrators)
            {
                if (!this.InitialAdministrators.Contains(securityPrincipal))
                {
                    // Add to registry.                    
                    this.AddSecurityPrincipal(securityPrincipal.SecurityPrincipal, SystemRoleType.TridentAdministrators);

                }
            }

            foreach (SecurityPolicyModel securityPrincipal in Users)
            {
                if (!this.InitialUsers.Contains(securityPrincipal))
                {
                    // Add to registry.
                    this.AddSecurityPrincipal(securityPrincipal.SecurityPrincipal, SystemRoleType.TridentUsers);
                }
            }

            foreach (SecurityPolicyModel securityPrincipal in Guests)
            {
                if (!this.InitialGuests.Contains(securityPrincipal))
                {
                    // Add to registry.

                    this.AddSecurityPrincipal(securityPrincipal.SecurityPrincipal, SystemRoleType.TridentGuests);

                }
            }
        }

        /// <summary>
        /// Shows the user or group picker window.
        /// </summary>
        private void ShowUserAndGroupPicker()
        {
            IEnumerable<ISecurityPrincipal> usersAndGroups = this.registryManagerService.FetchAllEnabledSecurityPrincipals(false);
            picker = new UsersAndGroupsPicker(usersAndGroups);
            picker.okButton.Click += new System.Windows.RoutedEventHandler(OnSelectingUserAndGroup);

            picker.Owner = System.Windows.Application.Current.MainWindow;
            picker.ShowDialog();
        }

        /// <summary>
        /// Called when selecting user and group.
        /// </summary>
        /// <param name="sender">UsersAndGroupsPicker UI.</param>
        /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
        private void OnSelectingUserAndGroup(object sender, System.Windows.RoutedEventArgs e)
        {
            if (null != picker.usersAndGroupsListView.SelectedItem)
            {
                bool isAlreadyExist = false;
                ISecurityPrincipal securityPrincipal =
                    (picker.usersAndGroupsListView.SelectedItem as SecurityPrincipalModel).SecurityPrincipal;

                SecurityPolicyModel securityPolicyModel = new SecurityPolicyModel(securityPrincipal);

                foreach (SecurityPolicyModel model in this.SelectedList)
                {
                    if ((model.SecurityPrincipal as INamedObject).Name == (securityPolicyModel.SecurityPrincipal as INamedObject).Name)
                    {
                        isAlreadyExist = true;
                        break;
                    }
                }

                if (!isAlreadyExist)
                {
                    this.SelectedList.Add(securityPolicyModel);
                }
            }
        }

        /// <summary>
        /// Closes the system policy UI.
        /// </summary>
        private void CloseSystemPolicyUI()
        {
            systemSecurity.Close();
        }

        #endregion

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Fires the property changed event.
        /// </summary>
        /// <param name="propertyName">Name of the property which has been modified</param>
        private void OnNotifyPropertyChanged(string propertyName)
        {
            if (this.PropertyChanged != null)
            {
                this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        #endregion

    }
}
